home *** CD-ROM | disk | FTP | other *** search
- (******************************************************************************
-
- EXTEND.PAS
- Version 1.5
- August 25, 1986
- by Randy Forgaard
- CompuServe 70307,521
-
-
- MS-DOS and PC-DOS Turbo Pascal 3.0 and higher only allow up to 15 files to be
- open at the same time, due to limitations in DOS. This file shows you how to
- have up to 96 files open simultaneously under DOS 2.0 or 2.1, or 252 files open
- simultaneously under DOS 3.0 or higher. Below is a description of how to use
- this technique, followed by a technical explanation of the implementation, for
- those who are interested.
-
-
- TO USE THIS TECHNIQUE:
-
- You need the routines and global declarations below. Everywhere in your
- program that you Reset or Rewrite a file "f" for the first time, insert an
- "OpenExtend(f);" invocation immediately after the Reset or Rewrite. At every
- place in your program that you call one of Turbo's built-in routines (other
- than Assign) for handling files (e.g., Read, Write, Close, Seek, Reset,
- Rewrite, etc.), put an "UnExtend(f);" invocation immediately prior to the call,
- and a "ReExtend(f);" invocation immediately after the call. Do not, however,
- insert UnExtend and ReExtend calls around the very first Reset or Rewrite that
- you use to initially open a file.
-
- At the very top of your program, prior to the "program" statement, put the
- compiler directive {$F252}. (You may use a value smaller than 252, if you
- wish. Under DOS 2.0/2.1, values above 96 have no additional benefit. Each
- larger value for the {$Fnnn} directive uses 2 additional bytes in the program's
- global data space.) The value you specify for the {$Fnnn} directive is the
- maximum number of files you will be able to have open at the same time in your
- program.
-
- Edit your CONFIG.SYS file (see the DOS manual for details), so that it includes
- a line that says "FILES=nnn". The value of "nnn" should be 3 greater than the
- value you specified for the {$Fnnn} directive (larger values will provide no
- additional benefit, with respect to your individual program), and should not
- exceed 99 (for DOS 2.0/2.1) or 255 (for DOS 3.0 and higher). Under any version
- of DOS, the minimum allowable value for "FILES=nnn" is 8. Then, reboot your
- computer so that the "FILES=nnn" parameter takes hold. That's it!
-
- Note: EXTEND.PAS uses a typed constant whose value changes during the course of
- execution. Consequently, when compiling any program that uses EXTEND.PAS you
- must either: 1) compile the program in memory and run it only in memory, or 2)
- compile the program to a .COM file, and execute the .COM file. The program
- will bomb if you compile and run the program in memory, and then immediately
- compile to a .COM file without first leaving and restarting Turbo.
-
- Running the sample program below will tell you the maximum number of files you
- can have open at once (usually 96 or 252, unless you have chosen a smaller
- value for the {$Fnnn} directive, or "FILES=nnn" has a value less than the
- maximum, or a resident program has files open, or there are some orphaned files
- due to previous program error). You will need to remove a "comment" line to
- run the program...see the instructions below. This program takes a while to
- run, due to the heavy disk I/O, so running it on a hard disk (or, even better,
- a RAM disk) is recommended. Make sure that you are running the program in a
- subdirectory, so that you don't run up against the DOS limit on the number of
- allowable files in the root directory of a drive. Enjoy!
-
- Note: Turbo/DOS normally closes all open files when a program aborts due to a
- run-time error or I/O error (though some data in Turbo Text output files will
- normally be lost in such cases, anyway). The "Extend" technique presented here
- defeats this automatic-close mechanism. This can leave lost clusters on the
- disk, and can cause the DCB Table to fill up (see below), if a run-time error
- or I/O error occurs while "extended" files are open. These are not serious
- conditions, though you may have to reboot the computer if a subsequent program
- aborts with not enough available file handles. Also, the lost sectors should
- be occasionally excised using "CHKDSK /F". To avoid these two problems,
- implement a user-written error handler that will capture all run-time errors
- and I/O errors, and allow a graceful exit by closing all of the "extended"
- files before exiting to DOS. For details on writing an error handler, see the
- Turbo manual or the README file on the Turbo distribution disk.
-
-
- THE TECHNICAL DETAILS:
-
- Much of the following information is not documented in the DOS Technical
- Reference manual.
-
- Under DOS 1.0 and 1.1, all files were accessed via File Control Blocks (FCB's).
- There was no limit to the number of FCB's that a program could use, so there
- was no limit to the number of files open simultaneously.
-
- Under DOS 2.0 and higher, an alternate (and preferable) method of accessing
- files was introduced, using a 2-byte integer called a "handle" to refer to a
- file. A "handle" file is described using a data structure called a Device
- Control Block (DCB). However, DOS provides the storage space for all DCB's,
- rather than having the application program store the DCB's, so the number of
- available DCB's is limited to the amount of space that DOS has set aside for
- them. The maximum number of files/devices that can be open simultaneously, on
- the whole computer, is the number of slots available in the DCB Table created
- by DOS. The DCB's in the DCB Table are consecutively numbered, starting with
- 0. DCB's 0, 1, and 2 are predefined by DOS to correspond to the AUX, CON, and
- PRN devices, respectively. All remaining DCB's in the DCB Table are available
- for files or devices used by application programs.
-
- So that I/O redirection can be supported, the DCB numbers are not used directly
- when accessing files. Instead, a file "handle" is used. A "handle" is an
- index into a 20-byte array, called the Handle Table, located at offset 18H of
- the Program Segment Prefix (PSP) for a program. (For a general discussion of
- the PSP, see the DOS Technical Reference manual). Each element of the Handle
- Table is the DCB number of a file or device. The value at index "handle" in
- the Handle Table is the DCB number of the file or device that implements that
- file handle. The handles are numbered from 0 to 19. Thus, if the value 8 is
- in the 6th byte of the Handle Table, the handle "5" refers to the file (or
- device) described by the DCB in slot 8 of the DCB Table (remember that handles
- are numbered starting at zero). If a handle is not currently being used, its
- entry in the Handle Table is FFH. DOS predefines the first 5 handles to be
- Stdin, Stdout, Stderr, Stdaux, and Stdprn, so the first 5 entries in the Handle
- Table are 1, 1, 1, 0, and 2, corresponding to the DCB numbers for the CON (1),
- AUX (0), and PRN (2) devices. This leaves only 15 available handles for
- opening files (or new devices).
-
- Every time a new handle file is opened, a new handle gets used, and the handle
- is freed when the handle file is closed. Since there are only 20 slots
- available in the Handle Table for a program, DOS only allows a "process" to
- have a maximum of 20 file handles in use simultaneously (and the first 5
- entries are predefined, as just noted, unless those handles get closed and
- reused). Every new handle file requires a unique handle, so only 20
- files/devices can be open at the same time by a single process (unless FCB's
- are used). (A "process" is any program that has been loaded into memory by
- DOS. There may be several processes in memory at once, if a multitasker is
- being used, or if one program invokes a child program, etc.) There can be many
- more than 20 DCB's in the DCB Table, so the real limitation, for each process,
- is the size of the Handle Table in the program's PSP.
-
- The size of the DCB Table (i.e., the maximum number of files/devices that can
- be open simultaneously in the whole computer) is controlled by the "FILES=nnn"
- entry in the CONFIG.SYS file. The minimum number of slots is 8. Under DOS
- 2.0/2.1, the maximum number is 99, and under DOS 3.0 and higher, the maximum is
- 255. As previously mentioned, the first three of these DCB slots are occupied
- by the AUX, CON, and PRN devices.
-
- A single program can use all of the DCB's in the DCB Table (except for the 3
- reserved by DOS) all on its own, by effectively bypassing the Handle Table in
- the PSP, except on a temporary basis. Instead of allowing DOS to store the DCB
- numbers in the Handle Table, the program can store these numbers elsewhere.
- Then, to manipulate a file using DOS, the program can temporarily put the DCB
- number of that file into a slot in the Handle Table, pass the index of that
- table slot (i.e., that "handle") to DOS, and DOS will operate on that
- handle/DCB number. After the DOS call, the program can remove that DCB number
- from the designated Handle Table slot, freeing up that handle for use in
- another DOS call for another file. In this way, DOS can be fooled into
- accessing up to 96 (or 252) different files/devices using a single handle entry
- in the Handle Table, because the only limit is the size of the DCB Table.
-
- The Handle Table slot that one decides to use, in the above scheme, might
- already be in use. I.e., the slot might contain a value other than FFH,
- indicating that it holds the actual DCB number of another open file. One can
- alleviate the conflict by saving that DCB number in a temporary variable before
- temporarily using that Handle Table slot to access DOS, then restore the DCB
- number to the slot afterward.
-
- The scheme outlined above, for keeping more than 20 files open at the same time
- in a single program, will work fine in an assembly language program. However,
- there is one remaining complication when using the technique with Turbo Pascal.
- Turbo maintains its own, internal list of all open handles at run-time, which
- we will call the Open Handle List. Turbo adds a handle to the list whenever a
- program opens a file using Reset or Rewrite, and then Turbo removes the handle
- from the list whenever a program does a Close.
-
- Turbo maintains the Open Handle List for the following reason: if a Turbo
- program terminates normally to DOS, DOS will automatically close all open
- handles. (Don't depend on this feature, though, for Text output files. Turbo
- maintains a Text buffer in RAM whose contents will be lost if the Text file is
- not closed using Turbo's Close routine.) However, if one invokes a Turbo
- program "in memory," directly from Turbo's main menu, DOS is not able to close
- the open handles upon termination (because Turbo itself is still running, so
- DOS doesn't know that the "in memory" Turbo program terminated). For this
- reason, Turbo takes over the duty of closing all open handles when a Turbo
- program terminates. To do so, Turbo maintains the Open Handle List.
-
- The maximum size of the Open Handle List is set by the rarely-used {$Fnnn}
- compiler directive that Turbo provides. Thus, {$F20} sets aside space for 20
- open handles on the list. If you attempt to open a file in a normal Turbo
- program that does not use the "Extend" technique outlined in this document, and
- you get an I/O error F3 ("Too many open files" -- not documented in early
- editions of the Turbo 3.0 manual), it can mean either: (1) there are no more
- available handles in the Handle Table of the PSP; (2) there are no more
- available DCB's in the global DCB Table; or (3) Turbo's Open Handle List is
- full. In practice, I/O error F3 usually means (1), because most people have
- (or should have) at least FILES=20 in their CONFIG.SYS file (so the DCB Table
- should still have some available entries), and the default value for the
- {$Fnnn} directive is 16 (which is greater than the 15 handles that are normally
- available in the Handle Table).
-
- The Open Handle List injects the following complication into the "Extend"
- scheme presented above: When Reset or Rewrite is used to open a Turbo file,
- Turbo adds that handle to the Open Handle List, as noted above. We then free
- up that Handle Table slot by copying the DCB number in that slot to another
- location, and store FFH into that slot. Now, when we want to access the file,
- we are free to temporarily put that DCB number into any Handle Table slot,
- perform the file access, and then restore any previous DCB number that may have
- been in that slot. However, when we Close the file, we must be sure to use
- exactly the same Handle Table slot as was used when the file was opened. This
- is because, when we execute the Turbo Close routine, Close must "see" the same
- handle as was used to open the file, so that it can successfully remove that
- handle from Open Handle List. If we do not use the same Handle Table slot when
- we Close the file, the original handle will not get removed from the Open
- Handle List, and the Open Handle List will start to fill up. Eventually, if
- files keep getting opened and closed, a Rewrite or Reset will result in an I/O
- error F3 (because the Open Handle List is full), even though there may be
- available slots in both the Handle Table and the DCB Table.
-
- In the "Extend" technique proposed here, we will always use the same Handle
- Table slot when accessing a given Turbo file, though several Turbo files may
- share the same Handle Table slot. The scheme works as follows: After a Reset
- or Rewrite that opens a Turbo file, Turbo stores the file handle as an integer
- value in the first two bytes of the FIB that Turbo uses to represent the file.
- In truth, the high-order byte of this handle will always be zero, because
- (currently) the only handle numbers that DOS supports are 0-19. Thus, when we
- are not using an "extended" file, we free up its Handle Table slot by copying
- the DCB number for that file into the second byte (the high-order byte) of the
- handle stored in the FIB. When we want to access the file, we look again at
- the first two bytes of the FIB. We copy the DCB number (stored in the second
- byte of the FIB) into the appropriate Handle Table slot (stored in the first
- byte of the FIB), zero out the second byte of the FIB (the high-order byte of
- the handle), and then invoke the desired Turbo file access routine(s). When we
- are through invoking those routines, we reverse the process, freeing up that
- Handle Table slot again. Since a given "extended" file is always accessed via
- the same Handle Table slot, Turbo's Open Handle List works correctly, and the
- spurious I/O error F3 is averted.
-
- The OpenExtend, UnExtend, and ReExtend routines below use this technique.
- OpenExtend(f) is used on a previously-opened file, "f." It moves f's DCB
- number into f's FIB, and stores an FFH into f's slot in the Handle Table.
- UnExtend(f) copies the current DCB number (if any) in f's slot of the Handle
- Table to a safe place, copies the DCB number of "f" to that slot, and
- zeroes-out the DCB number in the FIB, in preparation for its use by Turbo/DOS.
- ReExtend(f) moves f's DCB number back into the FIB, and restores the previous
- value (if any) of that slot in the Handle Table.
-
- To obtain the address of the Handle Table, which is at offset 18H in the PSP,
- the program needs to find the address of its PSP. Normally, this is very easy:
- when DOS loads a .COM file, the address of the PSP is just CS:0000. Using
- CS:0000 in this manner would be viable as long as we compile the program to a
- .COM file and execute the .COM file. However, if we run the program in memory,
- from the Turbo menu, Turbo makes a copy of its own PSP for use by the program.
- DOS still thinks of Turbo's PSP as being the "official" PSP for the running
- program, since DOS did not "see" Turbo invoke the program. Hence, CS:0000 is
- not the valid PSP address when the program is running in memory, since that is
- the address of the program's "fake" PSP rather than Turbo's PSP.
-
- To allow the program to work correctly both when running in memory and when run
- as a .COM file, we use the DOS function call 62H, "Get Program Segment Prefix
- Address (PSP)." This function call is available in DOS 3.0 and higher. There
- is an identical function call in DOS 2.0/2.1, but its function number is 51H,
- and it is not documented. Function 51H is also available in DOS 3.0/3.1.
- However, for upward-compatibility reasons with future versions of DOS, we will
- use the undocumented 51H function with DOS 2.0/2.1 (since we know 51H is
- available in those versions of DOS), and use 62H for DOS 3.0 and higher (since
- 62H is a documented function). There is no such function call in DOS 1.0/1.1,
- but the technique below will not work with those early versions of DOS anyway,
- since they did not provide file handles. To decide whether to use function 51H
- or 62H, we call DOS function 30H, "Get DOS Version Number," to determine which
- version of DOS is running. This strategy for obtaining the Handle Table
- address is implemented in the GetHandleTableAddr function, below, which gets
- called only once (the first time that OpenExtend is called).
-
- Note: The "Extend" technique presented here will not interfere with overlays in
- your program (since it only commandeers a Handle Table slot temporarily),
- provided that your program leaves at least one DCB available for use by the
- Turbo run-time library to read in overlay files.
-
- Many thanks to Bela Lubkin (CompuServe 73047,1112) for masterminding this idea,
- to Kim Kokkonen (CompuServe 72457,2131) for helping me debug it, and to Scott
- Bussinger (CompuServe 72247,2671), who discovered and fixed the insidious
- interaction with Turbo's Open Handle List. For more discussion of Handle
- Tables and the implementation of DOS redirection, please see Stan Mitchell,
- "Command Line Redirection," PC Tech Journal, January 1986, Page 44.
-
- Change Log:
-
- Version 1.0: Original release.
-
- Version 1.1: Changed to invoke a DOS function to get the PSP address, so that
- programs using this technique can be run in memory under Turbo.
-
- Version 1.2: Replaced some outdated comments.
-
- Version 1.3: Changed example program so that it erases the files it creates.
-
- Version 1.4: Added a caveat about compiling programs that use EXTEND.PAS.
-
- Version 1.5: Fixed the interaction with Turbo's Open Handle List, which
- previously causes spurious F3 I/O errors. Added a warning that
- EXTEND disables the Turbo/DOS automatic file-close feature, and
- how to work around it.
-
- ******************************************************************************)
-
-
- {$F252}
-
- const
- LastHandle = 19; {Highest-numbered handle}
- UnusedHandle = $FF; {DcbTable entry that denotes an unused handle}
- type
- HandleTable = array[0..LastHandle] of Byte;
- HandleTablePtr = ^HandleTable;
- const
- TablePtrOk: Boolean = false; {"True" iff TablePtr is initialized}
- var
- TablePtr: HandleTablePtr; {Points to Handle Table for this process}
- SaveDcb: Byte; {Temporary variable for a DCB number during a function call}
-
-
- {Internal routine. Returns the address of the Handle Table, which is at offset
- 18H in the PSP.}
-
- function GetHandleTableAddr: HandleTablePtr;
- var
- regs: record
- case Integer of
- 1: (AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags: Integer);
- 2: (AL, AH, BL, BH, CL, CH, DL, DH: Byte)
- end;
- begin
- regs.AH := $30;
- MsDos(regs); {Get DOS version number}
- case regs.AL of
- {If running under a DOS earlier than 2.0, force a run-time error}
- 0: regs.AL := regs.AL div 0;
- 2: regs.AH := $51; {Undocumented, but works with DOS 2.0/2.1 (and 3.X)}
- else regs.AH := $62 {Works with DOS 3.0 and higher}
- end;
- MsDos(regs); {Get PSP address}
- GetHandleTableAddr := Ptr(regs.BX, $18)
- end {GetHandleTableAddr};
-
-
- {Causes "f" to become an "extended" file; i.e., to remain open without using up
- any file handles. The parameter "f" must be any Turbo file; e.g., a File, a
- File of Byte, a File of Foo, Text, etc. This routine should be called
- immediately after the Reset or Rewrite that is initially used to open "f."
- After "f" has become extended, it cannot be used by Turbo's built-in file
- routines until it has been unextended using UnExtend.}
-
- procedure OpenExtend (var f);
- var
- fib: record handle, dcbnum: Byte end absolute f;
- begin
- if not TablePtrOk then
- begin
- TablePtr := GetHandleTableAddr;
- TablePtrOk := true
- end;
-
- {If the "dcbnum" byte is already in use, a futuristic DOS must be running
- that allows more than 255 handles in a single process. In this case, force
- a run-time error.}
- if fib.dcbnum <> 0 then fib.dcbnum := fib.dcbnum div 0;
-
- fib.dcbnum := TablePtr^[fib.handle];
- TablePtr^[fib.handle] := UnusedHandle
- end {OpenExtend};
-
-
- {Unextends the extended file "f," so that it can be used by any of Turbo's
- built-in file routines. Note that "f" must have been converted to an extended
- file by OpenExtend before invoking UnExtend(f). After calling UnExtend, and
- then invoking the Turbo file function on "f," ReExtend(f) should be invoked
- immediately to re-extend "f" and restore the DOS file state information.}
-
- procedure UnExtend (var f);
- var
- fib: record handle, dcbnum: Byte end absolute f;
- begin
- SaveDcb := TablePtr^[fib.handle];
- TablePtr^[fib.handle] := fib.dcbnum;
- fib.dcbnum := 0
- end {UnExtend};
-
-
- {Re-extends "f" into an extended file. Note that "f" must have been converted
- to an extended file by OpenExtend, and then unextended using UnExtend, before
- "f" can re-extended using ReExtend. ReExtend(f) should be invoked immediately
- after any normal Turbo file function call using "f."}
-
- procedure ReExtend (var f);
- var
- fib: record handle, dcbnum: Byte end absolute f;
- begin
- fib.dcbnum := TablePtr^[fib.handle];
- TablePtr^[fib.handle] := SaveDcb
- end {ReExtend};
-
-
-
- {Example program -- remove the "(*" line below to enable the program (a Bela
- Lubkin trick). This program opens as many Text files as it can, until DOS
- runs out of room in its DCB Table. It then reports how many files were
- successfully opened, writes a line to each of them, then closes and erases
- each of them. Note: The value of the "FILES=nnn" parameter in the CONFIG.SYS
- file must be set to an appropriate value (see above). If you change the
- "FILES=nnn" value, be sure to reboot before running this program.
-
- This program takes a while to run, due to the heavy disk I/O, so running it on
- a hard disk (or, even better, a RAM disk) is recommended. Make sure that you
- are running the program in a subdirectory, so that you don't run up against
- the DOS limit on the number of allowable files in the root directory of a
- drive.}
-
- (*
- const
- MaxCount = 255;
- var
- num: string[6];
- f: array[1..MaxCount] of Text;
- i, count: Integer;
- result: Byte;
- begin
- writeln('Opening files...');
- i := 0;
- repeat
- i := i + 1;
- Str(i, num);
- Assign(f[i], 'junk' + num + '.txt');
- {$I-} Rewrite(f[i]); {$I+}
- result := IOResult;
- if result = 0 then OpenExtend(f[i])
- until result <> 0;
- count := i - 1;
- writeln('Successfully opened ', count, ' files at the same time. ',
- 'Writing to each file...');
- for i := 1 to count do
- begin
- UnExtend(f[i]);
- writeln(f[i], 'This is a test');
- ReExtend(f[i])
- end;
- writeln('Closing and erasing each file...');
- for i := 1 to count do
- begin
- UnExtend(f[i]);
- Close(f[i]);
- Erase(f[i]);
- ReExtend(f[i])
- end;
- writeln('Done.')
- end.
- (**)